# -*- coding: utf-8 -*-
import Tkinter
from Tkinter import *
import struct
from struct import *
import tkFileDialog
import tkMessageBox
import csv
import os

VERSION = '1.0'

class upiHelper(object):
	"""
	creates Sygic UPI file
	"""
	def __init__(self,output,icon,name,points): #filename, filename, array
		self.output = output
		self.icon = icon
		self.name = name
		self.points = points #long, lat, name, 

	def NameLenght(self):
		"""
		First byte is the length of the POI title will follow this byte
		"""
		lenght = len(self.name)
		lenght = lenght*2+2 #two bytes utf-16 + 2 null bytes
		return chr(lenght)

	def Name(self):
		"""
		A string with the name of the POI file without extension (remember:
		everything multiplied by 2 since it is utf-16 (2 bytes per letter)
		"""
		file_hex = str(self.name).encode('utf-16')
		file_hex_clean = file_hex.strip('\xff\xfe') #remove \xff\xfe (BOM)
		#name + null + 0x02 + 9 null
		return file_hex_clean + 2*chr(0) + chr(2) + 9*chr(0)
	
	def IconNameLenght(self):
		"""
		length of the utf-16 BMP filename that will follow this byte
		"""
		lenght = len(os.path.basename(self.icon))
		lenght = lenght*2+2 #+2 null bytes
		return chr(lenght)

	def IconName(self):
		"""
		A string with the BMP file name including the extension
		"""
		file = os.path.basename(self.icon)
		file_hex = str(file).encode('utf-16')
		file_hex_clean = file_hex.strip('\xff\xfe')
		return file_hex_clean
		
	def Skipper(self):
		"""
		Coordinates are stored as 4−byte integers representing a WGS84
		longitude or latitude, multiplied by 100,000 and rounded to the
		nearest integer. As such, an X−coordinate should always be a value
		between −18,000,000 and +18,000,000, and a Y−coordinate should be
		a value between −9,000,000 and +9,000,000.
		
		1 byte T: type (always 1)
		4 bytes Number of bytes in the file, including and starting at this
		 record, that contain data for POI enclosed in the given
		 rectangle
		4 bytes X1: longitude coordinate of the west edge of the rectangle
		4 bytes Y1: latitude coordinate of the south edge of the rectangle
		4 bytes X2: longitude coordinate of the east edge of the rectangle
		4 bytes Y2: latitude coordinate of the north edge of the rectangle
		"""
		X = []
		Y = []

		for i in range(0, len(self.points)):
			X.append(int(float(self.points[i][0]) * 100000))
		for i in range(0, len(self.points)):
			Y.append(int(float(self.points[i][1]) * 100000))

		Xmin = struct.pack('l', min(X))
		Ymin = struct.pack('l', min(Y))
		Xmax = struct.pack('l', max(X))
		Ymax = struct.pack('l', max(Y))

		byteT = chr(1)

		N = 1+4+4+4+4+4 #byteT + byteN + Xmin + Ymin + Xmax + Ymax 
		for i in range(0, len(self.points)):
			N += 1+4+4+4+2*len(self.points[i][2]) #pointbytes
		byteN = struct.pack('l', N)
		#byteT + byteN + Xmin + Ymin + Xmax + Ymax + pointbytes + 2 null
		
		skipper = byteT + byteN + Xmin + Ymin + Xmax + Ymax
		return skipper

	def Points(self):
		"""
		1 byte T: type (always 3)
		4 bytes L: length of this record in bytes (including the T and L)
		4 bytes X: longitude of the POI
		4 bytes Y: latitude of the POI
		L−13 bytes Name: utf-16 name of POI
		"""
		byteXYN = ''
		for i in range(0, len(self.points)):
			byteT = chr(3) 
			byteL = struct.pack('l', 1+4+4+4+2*len(self.points[i][2])+2)
			#bytT + byteL + Byte X + Byte Y + Name(utf-16) + 2 null

			X = struct.pack('l', int(float(self.points[i][0]) * 100000))
			Y = struct.pack('l', int(float(self.points[i][1]) * 100000))

			Name_hex = self.points[i][2].encode('utf-16')
			Name_hex_clean = Name_hex.strip('\xff\xfe') #remove \xff\xfe (BOM)

			byteXYN += byteT + byteL + X + Y + Name_hex_clean + 2*chr(0)

		return byteXYN

	def Write(self):
		with open(self.output, "wb") as output:
			output.write(self.NameLenght())
			output.write(self.Name())
##			output.write(2*chr(0)) #filler record 0x00
##			output.write(chr(2)) #filler record 0x02
##			output.write(9*chr(0)) #followed by 9 0x00 bytes
			output.write(self.IconNameLenght())
			output.write(self.IconName())
			output.write(2*chr(0)) #filler record 0x02
			output.write(self.Skipper())
			output.write(self.Points())
		tkMessageBox.showinfo("Aviso", "Completo!\n\n%d pontos convertidos."
							  % len(self.points))

class textToArrayHelper(object):
	"""
	import sygic text file into an array
	"""
	def __init__(self, textfile):
		self.textfile = textfile
		self.lenght = 0
		self._array = []

	def txtToArray(self):
		with open(self.textfile, 'rb') as tf:
			try:
				data = csv.reader(tf, delimiter='|', quotechar='"')
				for row in data:
					self._array.append(row)

				self.lenght = len(self._array)
				return self._array
					
			except csv.Error as e:
				tkMessageBox.showinfo("ERROR", "file %s, line %d: %s" %
									  (tf.name, reader.line_num, e))
				
	def arrayLenght(self):
		return self.lenght

	def _array(self):
		return a

class fileHelper(object):
	"""
	file dialogs / filling fields 
	"""
	def setInput(self):
		self.input = tkFileDialog.askopenfilename(defaultextension = '.txt',
		filetypes=[('Sygic Text File', '.txt'),('All files', '.*')],
		title="Open")
		return self.input
	
	def setOutput(self):
		self.input = tkFileDialog.asksaveasfilename(defaultextension =
		'.upi', initialfile = "POI.upi", filetypes=[('UPI Files',
		'.upi'),('All files', '.*')], title="Save As",
		initialdir=os.path.expanduser('~'))
		return self.output

	def setOutputAsInput(self):
		self.output = self.input.replace(".txt",".upi")
		i = 1
		while os.path.isfile(self.output) is True: #if file already exists
			self.output = self.input.replace(".txt","(%d).upi" % i)
			i += 1
		return self.output
	
	def setIcon(self):
		self.icon = tkFileDialog.askopenfilename(defaultextension = '.bmp',
		filetypes=[('Bitmap', '.bmp'), ('All files', '.*')], title="Open")
		return self.icon

	def setIconAsInput(self):
		if os.path.isfile(self.input.replace(".txt",".bmp")):
			self.icon = self.input.replace(".txt",".bmp")
			return self.icon
		else:
			return ''

	def setTitle(self):
		self.title = os.path.splitext(os.path.basename(self.input))[0]
		return self.title
	
"""
BUTTONS FUNCTIONS
"""

files = fileHelper()

def chooseInput():
	input = files.setInput()
	filename_input.set(input)
	
	if input[-4:] == '.txt': #auto define output
		output = files.setOutputAsInput()
		filename_output.set(output) #fill field

	title = files.setTitle()
	name_title.set(title)
	icon = files.setIconAsInput()
	filename_icon.set(icon)
		
def chooseOutput():
	output = files.setOutput()
	filename_output.set(output)
	
def chooseIcon():
	icon = files.setIcon()
	if icon[-4:] != '.bmp': 
		tkMessageBox.showinfo("ERROR", "File must be a .bmp " \
		"(bitmap)!")
	else:
		filename_icon.set(icon)

"""
BUTTON CONVERT
"""

def OK():
	try:
		array_points = textToArrayHelper(filename_input.get())
		array_points.txtToArray()
		
		upi = upiHelper(filename_output.get(),
						filename_icon.get(),
						name_title.get(),
						array_points._array)
		upi.Write()
		
	except Exception as e:
		tkMessageBox.showinfo("ERROR", "There was an error reading data. "\
							  "\nCheck if the file is empty. If any UPI file " \
							  "or if it uses a vertical bar '|' as "\
							  "separator. Any UPI file created may be " \
							  "corrupted.\n\n%s\n%s\n%s" \
							  % (str(type(e)),str(e.args),str(e)))
"""
USER INTERFACE
"""

app = Tk()
app.title("Demian Tools - Convert TXT to UPI - v.%s" % VERSION)  

window = LabelFrame(app, text=" Select Files: ")
window.grid(row=0, sticky='W', padx=5, pady=5, ipadx=5, ipady=5)
        
#input
input = Label(window, text="Input File:")
input.grid(row=0, column=0, sticky=E)
filename_input = StringVar() #var tk trackable
entry_input = Entry(window,width=50,textvariable=filename_input)
entry_input.grid(row=0, column=1, sticky=W)
button_input = Button(window, text="Choose", command = chooseInput)
button_input.grid(row=0, column=2, padx=5)

#output
label_output = Label(window, text="Output File:")
label_output.grid(row=1, column=0, sticky=E)
filename_output = StringVar()
entry_output = Entry(window, width=50, textvariable=filename_output)
entry_output.grid(row=1, column=1, sticky=W)
button_output = Button(window, text="Choose", command = chooseOutput)
button_output.grid(row=1, column=2, padx=5)

#icon
label_icon = Label(window, text="POI Icon:")
label_icon.grid(row=2, column=0, sticky=E)
filename_icon = StringVar()
entry_icon = Entry(window, width=50, textvariable=filename_icon)
entry_icon.grid(row=2, column=1, sticky=W)
button_icon = Button(window, text="Choose", command = chooseIcon)
button_icon.grid(row=2, column=2, padx=5)

#title
label_title = Label(window, text="POI Title:")
label_title.grid(row=3, column=0, sticky=E)
name_title = StringVar()
entry_title = Entry(window, width=50, textvariable=name_title)
entry_title.grid(row=3, column=1, sticky=W)

#button Convert
buttonOK = Button(app, text="Convert File", command = OK, width=10)
buttonOK.grid(row=1, column=0, sticky=E, padx=5, pady=5)

#label info
frame_info = LabelFrame(app, text=" How it works ")
frame_info.grid(row=2, sticky='WE', padx=5, pady=5, ipadx=5, ipady=5)

info = "This application will convert a text file into Sygic UPI (Points of " \
	   "Interest) file. The\ntext file should be in the the same form as Sygic " \
	   "suggests, like:\n\n-56.208280|-34.906960|Palacio Taranco\n\nwhere first " \
	   "coordinate is longitude, then latitude followed by the name of poi." \
	   "\nWhen clicking the choose button to search for a text file, the " \
	   "application will\natempt to auto-fill the fields below. If the bitmap " \
	   "has the same name as the\ntext file, then it will be auto-filled as well. " \

label_info = Label(frame_info, text=info, justify=LEFT)
label_info.grid(row=0, column=0, sticky=W)

#create window
window.mainloop()

